package com.bird.generate.ddd.domain.generateutil

import cn.hutool.core.io.FileUtil
import cn.hutool.core.util.StrUtil
import com.bird.generate.ddd.domain.*
import com.bird.generate.ddd.domain.vo.Field
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter

class DomainGenerate {

    companion object {
        /**
         * 生成领域代码
         */
        fun domainGenerate(path: String,aggregateRoot: AggregateRootEntity) {
            var domainRootPath = path.appendNextPath(aggregateRoot.moduleName.lowercase()+"bc").appendNextPath("${aggregateRoot.moduleName.lowercase()}bc-domain").appendNextPath(JAVA_PATH)
                .appendNextPath("${aggregateRoot.comPrefix.replace(".","/")}/${aggregateRoot.projectName.lowercase()}prd/${aggregateRoot.moduleName.lowercase()}bc/domain/${aggregateRoot.enName.lowercase()}aggr")
            //root 包路径
            var aggregateJavaPath = "${aggregateRoot.comPrefix.replace(".","/")}/${aggregateRoot.projectName.lowercase()}prd/${aggregateRoot.moduleName.lowercase()}bc/domain/${aggregateRoot.enName.lowercase()}aggr".replace("/", ".")
            //生成聚合根
            generateRoot(aggregateRoot, domainRootPath, aggregateJavaPath)
            var valueObjects = aggregateRoot.valueObjects
            valueObjects = valueObjects + ValueObjectEntity(
                aggregateRoot.enName + "Id",
                aggregateRoot.name + "Id",
                aggregateRoot.description,
                false,
                listOf(
                    Field(
                        "${StrUtil.upperFirst(aggregateRoot.enName)}Id",
                        "${aggregateRoot.name}主键",
                        "Long",
                        true,
                        true
                    )
                )
            )
            var entities = aggregateRoot.entities
            if (entities.isNotEmpty()) {
                for (entity in entities) {
                    generateEntity(aggregateRoot,entity, domainRootPath, aggregateJavaPath)
                    valueObjects = valueObjects + ValueObjectEntity(
                        entity.enName + "Id",
                        entity.name + "Id",
                        entity.description,
                        false,
                        listOf(
                            Field("${StrUtil.upperFirst(entity.enName)}Id", "${entity.name}主键", "Long", true, true)
                        )
                    )
                }
            }
            valueObjects.forEach {
                //生成值对象
                generateValueObject(aggregateRoot,it, domainRootPath, aggregateJavaPath)
            }
            //生成映射
            generateMapping(aggregateRoot,valueObjects, domainRootPath, aggregateJavaPath)
            //生成错误码
            generateResultCode(aggregateRoot, domainRootPath, aggregateJavaPath)
            //生成仓储
            generateRepository(aggregateRoot, domainRootPath, aggregateJavaPath)
            //生成领域服务
            generateDomainService(aggregateRoot, domainRootPath, aggregateJavaPath)
            //生成pom.xml
            generatePomXml(aggregateRoot,path.appendNextPath(aggregateRoot.moduleName.lowercase()+"bc").appendNextPath("${aggregateRoot.moduleName.lowercase()}bc-domain"))
            //生成validator
            generateValidator(domainRootPath)
            //生成资源网关
            generateResourceGateway(aggregateRoot, domainRootPath, aggregateJavaPath)
            //生成领域事件
            generateDomainEvent(domainRootPath)
            //domainRootPath中最后一个/后边的不要了
            generatePackageInfo(domainRootPath.substringBeforeLast("/"), aggregateJavaPath.substringBeforeLast("."))
        }

        private fun generateDomainEvent(string: String) {
            FileUtil.mkdir(string.appendNextPath("domainevent/"))
        }

        private fun generateResourceGateway(
            aggregateRoot: AggregateRootEntity,
            domainRootPath: String,
            aggregateJavaPath: String
        ) {

            val resourceGatewayContent = """
package ${aggregateJavaPath}.port;

 /**
  * ${aggregateRoot.name}资源网关
  * @author ${aggregateRoot.creator}<p>
  * ================================<p>
  * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
  * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
  * ================================
  */
public interface ${StrUtil.upperFirst(aggregateRoot.enName)}DomainResourceGateway {
   
}
                """
            createFile(domainRootPath.appendNextPath("port/${StrUtil.upperFirst(aggregateRoot.enName)}DomainResourceGateway.java"), resourceGatewayContent)
        }

        private fun generateValidator(path: String) {
            FileUtil.mkdir(path.appendNextPath("validator/"))
        }

        /**
         * 生成pom.xml
         */
        private fun generatePomXml(rootEntity: AggregateRootEntity, path: String) {
            val pomContent =
"""<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>${rootEntity.comPrefix}</groupId>
        <artifactId>${rootEntity.moduleName.lowercase()}bc</artifactId>
        <version>${rootEntity.version}</version>
    </parent>

    <artifactId>${rootEntity.moduleName.lowercase()}bc-domain</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.zz</groupId>
            <artifactId>zz-core-ddd</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zz</groupId>
            <artifactId>zz-core-tool</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zz</groupId>
            <artifactId>zz-core-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
            """
            createFile(path.appendNextPath("pom.xml"), pomContent)
        }

        private fun generateDomainService(
            aggregateRoot: AggregateRootEntity,
            domainRootPath: String,
            javaPath: String
        ) {
            val serviceContent = """
package $javaPath;

import $javaPath.port.${StrUtil.upperFirst(aggregateRoot.enName)}CommandRepository;
import $javaPath.valueobject.*;
import com.zz.core.log.exception.ServiceException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * ${aggregateRoot.name} 领域服务
 * @author ${aggregateRoot.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
@Service
@RequiredArgsConstructor
public class ${StrUtil.upperFirst(aggregateRoot.enName)}DomainService {
    //${aggregateRoot.name} 仓储接口(命令)
    private final ${StrUtil.upperFirst(aggregateRoot.enName)}CommandRepository commandRepository;
    
}

            """
            createFile(domainRootPath.appendNextPath("${StrUtil.upperFirst(aggregateRoot.enName)}DomainService.java"), serviceContent)
        }

        private fun generateRepository(
            aggregateRoot: AggregateRootEntity,
            domainRootPath: String,
            javaPath: String
        ) {
            val repositoryContent = """
package $javaPath.port;

import java.util.Optional;
import $javaPath.${StrUtil.upperFirst(aggregateRoot.enName)}AggregateRootEntity;

 /**
  * ${aggregateRoot.name} 仓储接口(命令)
  * @author ${aggregateRoot.creator}<p>
  * ================================<p>
  * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
  * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
  * ================================
  */
public interface ${StrUtil.upperFirst(aggregateRoot.enName)}CommandRepository {
    /**
     * 保存${aggregateRoot.name}
     * @param ${StrUtil.lowerFirst(aggregateRoot.enName)}Entity ${aggregateRoot.name}聚合根实体
     */
    void save${StrUtil.upperFirst(aggregateRoot.enName)}(${StrUtil.upperFirst(aggregateRoot.enName)}AggregateRootEntity ${StrUtil.lowerFirst(aggregateRoot.enName)}Entity);
    
}
            """
            createFile(domainRootPath.appendNextPath("port/${StrUtil.upperFirst(aggregateRoot.enName)}CommandRepository.java"), repositoryContent)
        }

        /**
         * 创建ResultCode文件
         */
        private fun generateResultCode(
            aggregateRoot: AggregateRootEntity,
            domainRootPath: String,
            javaPath: String
        ) {
            val resultCodeContent = """
package $javaPath;

import com.zz.core.tool.api.IResultCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
 * ${aggregateRoot.name} 错误码
 *
 * @author ${aggregateRoot.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
 //todo 重写编码
@Getter
@RequiredArgsConstructor
public enum ${StrUtil.upperFirst(aggregateRoot.enName)}ResultCode implements IResultCode {
    //值对象参数校验未通过 
    VO_PARAM_VALID_ERROR(400, "002-14-P-001", "${aggregateRoot.name}值对象参数校验未通过"),
    //实体参数校验未通过
    ENTITY_PARAM_VALID_ERROR(400, "002-14-P-002", "${aggregateRoot.name}实体参数校验未通过"),

    ;
    /**
     * 响应码
     */
    private final int code;
    /**
     * 错误码
     */
    private final String errorCode;

    /**
     * 错误信息
     */
    private final String message;
}

            """
            createFile(domainRootPath.appendNextPath("${StrUtil.upperFirst(aggregateRoot.enName)}ResultCode.java"), resultCodeContent)
        }

        /**
         * 生成实体
         */
        private fun generateEntity(
            aggregateRoot: AggregateRootEntity,
            entity: EntityEntity,
            aggregateRootPath: String,
            packagePath: String
        ) {
            val rootClass = if(aggregateRoot.multiTenant) "BaseTenantEntity" else "BaseEntity"
            val entityName = StrUtil.upperFirst(entity.enName) + "Entity"
            val fields = entity.fields

            var entityContent = """
package $packagePath;

import $packagePath.valueobject.*;
import com.zz.core.ddd.base.${rootClass};
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

import java.util.Optional;

/**
 * ${entity.name} 实体
 * @author ${aggregateRoot.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
@Getter
@SuperBuilder
@Setter(AccessLevel.PRIVATE)
@ToString
public class $entityName extends ${rootClass}<${StrUtil.upperFirst(entity.enName)}Id> {
    ${fields.map {
                """
    /**
     * ${it.name}
     */
    private ${if(it.type.isNullOrBlank())StrUtil.upperFirst(it.enName)else it.type} ${StrUtil.lowerFirst(it.enName)};"""}.joinToString("")}
}"""
            createFile("$aggregateRootPath/$entityName.java", entityContent)

        }

        /**
         * 生成聚合根
         */
        private fun generateRoot(
            aggregateRoot: AggregateRootEntity,
            aggregateRootPath: String,
            packagePath: String
        ) {
            val rootClass = if(aggregateRoot.multiTenant) "TenantAggregateRoot" else "AggregateRoot"
            val aggregateRootName = StrUtil.upperFirst(aggregateRoot.enName) + "AggregateRootEntity"
            val fields = aggregateRoot.fields

            var entityContent = """
package $packagePath;

import $packagePath.valueobject.*;
import com.zz.core.ddd.base.${rootClass};
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

import java.util.Optional;

/**
 * ${aggregateRoot.name} 领域模型
 * @author ${aggregateRoot.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
@Getter
@SuperBuilder
@Setter(AccessLevel.PRIVATE)
@ToString
public class $aggregateRootName extends ${rootClass}<${StrUtil.upperFirst(aggregateRoot.enName)}Id> {
    ${fields.map {
                """          
    /**
     * ${it.name}
     */
    private ${if(it.type.isNullOrBlank())StrUtil.upperFirst(it.enName)else it.type} ${StrUtil.lowerFirst(it.enName)};"""}.joinToString("")}
}
            """
            createFile("$aggregateRootPath/$aggregateRootName.java", entityContent)
        }
        /**
         * 生成值对象
         */
        private fun generateValueObject(
            rootEntity: AggregateRootEntity,
            vo: ValueObjectEntity,
            rootPath: String,
            rootJavaPath: String
        ) {
            val voPath = rootPath.appendNextPath("valueobject")
            val voJavaPath = rootJavaPath + ".valueobject"

            if (vo.fields.isEmpty()) {
                vo.fields = listOf(
                    Field(
                        enName = "value",
                        name = vo.name,
                        type = vo.type,
                    )
                )
            }

            var requiredFields = vo.fields.filter { it.required }
            var toEquals = vo.fields.filter { it.toEquals }

            if (vo.isEnum) {
                var enumContent = """
package ${voJavaPath};

import cn.hutool.core.util.ObjectUtil;
import com.zz.core.ddd.base.ValueObject;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;
import java.util.Optional;
/**
 * ${vo.name} 值对象
 * @author ${rootEntity.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
@Getter
@RequiredArgsConstructor
public enum ${StrUtil.upperFirst(vo.enName)} implements ValueObject<${StrUtil.upperFirst(vo.enName)}> {

    ;
    /**
     * 编码
     */
    private final String code;
    /**
     * 介绍
     */
    private final String info;
    
    public static Optional<${StrUtil.upperFirst(vo.enName)}> fromCode(String code) {
        if (ObjectUtil.isEmpty(code)) {
            return Optional.empty();
        }
        return Arrays.stream(${StrUtil.upperFirst(vo.enName)}.values())
                .filter(type -> ObjectUtil.equals(type.code,code))
                .findFirst();
    }
    
    @Override
    public boolean sameValueAs(${StrUtil.upperFirst(vo.enName)} other) {
        return this == other;
    }
}  
                """
                createFile("$voPath/${StrUtil.upperFirst(vo.enName)}.java", enumContent)
                return
            }
            var voContent = """
package ${voJavaPath};

import ${rootJavaPath}.${StrUtil.upperFirst(rootEntity.enName)}ResultCode;
import com.zz.core.ddd.base.ValueObject;
import com.zz.core.log.exception.ServiceException;
import com.zz.core.tool.utils.Func;
import lombok.Getter;
import lombok.ToString;

/**
 * ${vo.name} 值对象
 * @author ${rootEntity.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
@Getter
@ToString
public class ${StrUtil.upperFirst(vo.enName)} implements ValueObject<${StrUtil.upperFirst(vo.enName)}> {
    ${vo.fields.map {
                """
    /**
     * ${it.name}
     */
    private final ${StrUtil.upperFirst(it.type)} ${StrUtil.lowerFirst(it.enName)};"""}.joinToString("")}
    
    public ${StrUtil.upperFirst(vo.enName)}(${vo.fields.map{"${StrUtil.upperFirst(it.type)} ${StrUtil.lowerFirst(it.enName)}"}.joinToString(",")}) {
        ${if(requiredFields.isNotEmpty()) {
            requiredFields.map{ 
                """
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            throw new ServiceException(${StrUtil.upperFirst(rootEntity.enName)}ResultCode.VO_PARAM_VALID_ERROR, "${it.name}不能为空");
        }"""
            }.joinToString("")
        }
            else ""
        }
        ${vo.fields.map{"this.${StrUtil.lowerFirst(it.enName)} = ${StrUtil.lowerFirst(it.enName)};"}.joinToString("\n        ")}
    }
    /**
     * 判断是否与其他值相同
     *
     * @param other 其他值
     * @return 判断结果
     */
    @Override
    public boolean sameValueAs(${StrUtil.upperFirst(vo.enName)} other) {
        return Func.isNotEmpty(other) 
            && ${toEquals.map{"Func.equals(this.${StrUtil.lowerFirst(it.enName)}, other.${StrUtil.lowerFirst(it.enName)})"}.joinToString("\n            && ")};
    }
}
            """
            createFile("$voPath/${StrUtil.upperFirst(vo.enName)}.java", voContent)
        }
        /**
         * 生成映射
         */
        private fun generateMapping(
            rootEntity: AggregateRootEntity,
            vos: List<ValueObjectEntity>,
            rootPath: String,
            rootJavaPath: String
        ) {

            val mappingContent = """
package $rootJavaPath;

import ${rootJavaPath}.valueobject.*;
import com.zz.core.tool.utils.Func;
import org.mapstruct.Named;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * ${rootEntity.name}映射
 * @author ${rootEntity.creator}<p>
 * ================================<p>
 * Date: ${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))}<p>
 * Time: ${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))}<p>
 * ================================
 */
public class ${StrUtil.upperFirst(rootEntity.enName)}Mapping {

    ${vos.map {
                if (it.fields.size == 1) {
                    if (it.isEnum) {
                        """
    /**
     * 转换为值对象 ${it.name}
     */
    public ${StrUtil.upperFirst(it.enName)} to${StrUtil.upperFirst(it.enName)}(String ${StrUtil.lowerFirst(it.enName)}){
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            return null;
        }
        return ${StrUtil.upperFirst(it.enName)}.fromCode(${StrUtil.lowerFirst(it.enName)}).orElse(null);
    }
    /**
     * 转换为原始类型 ${it.name}
     */
    public String toString(${StrUtil.upperFirst(it.enName)} ${StrUtil.lowerFirst(it.enName)}) {
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            return null;
        }
        return ${StrUtil.lowerFirst(it.enName)}.getCode();
    }
                    """
                    }else {
                        """
    /**
     * 转换为值对象 ${it.name}
     */
    public ${StrUtil.upperFirst(it.enName)} to${StrUtil.upperFirst(it.enName)}(${it.fields[0].type} ${StrUtil.lowerFirst(it.enName)}){
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            return null;
        }
        return new ${StrUtil.upperFirst(it.enName)}(${StrUtil.lowerFirst(it.enName)});
    }
    /**
     * 转换为原始类型 ${it.name}
     */
    public ${StrUtil.upperFirst(it.fields[0].type)} to${it.fields[0].type}(${StrUtil.upperFirst(it.enName)} ${StrUtil.lowerFirst(it.enName)}) {
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            return null;
        }
        return ${StrUtil.lowerFirst(it.enName)}.get${StrUtil.upperFirst(it.fields[0].enName)}();
    }
                    """
                    }
                }else {
                    it.fields.map {field->
                        """
    /**
     * 转换为值对象 ${field.name}
     */
    @Named("to${StrUtil.upperFirst(field.enName)}")
    public ${StrUtil.upperFirst(field.type)} to${StrUtil.upperFirst(field.enName)}(${StrUtil.upperFirst(it.enName)} ${StrUtil.lowerFirst(it.enName)}) {
        if (Func.isEmpty(${StrUtil.lowerFirst(it.enName)})) {
            return null;
        }
        return ${StrUtil.lowerFirst(it.enName)}.get${StrUtil.upperFirst(field.enName)}();
    }
                    """}.joinToString("\n")
                }

            }.joinToString("\n")}
}
            """
            createFile("$rootPath/${StrUtil.upperFirst(rootEntity.enName)}Mapping.java", mappingContent)
        }
    }

}